/***************************************************************************
 *  Copyright (c) 2005 Chih-Chung Chang                                    *
 *  Parts copyright (c) 2006 Michael Olson                                 *
 *  Copyright (C) 2007-2008 by Fehmi "Canavar" SAGLAM                      *
 *  canavar@fehmicans.net                                                  *
 *  Modified by david@tuxbrain for porting to Wiz                                                                       *
 *  This file is part of termula2x.                                        *
 *                                                                         *
 *  sTerm is free software; you can redistribute it and/or modify          *
 *  it under the terms of the GNU General Public License as published by   *
 *  the Free Software Foundation; either version 3 of the License, or      *
 *  (at your option) any later version.                                    *
 *                                                                         *
 *  sTerm is distributed in the hope that it will be useful,               *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 *  GNU General Public License for more details.                           *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>   *
 ***************************************************************************/

#include <stdlib.h>
#include <string>
#include <signal.h>
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <pty.h>
#include <sys/time.h>
#include "../include/sterm.h"
#include "../include/font.h"
#include "../simpleini/SimpleIni.h"
#include "../include/utilities.h"
#include "../include/stermvalueparser.h"
#include "../include/touchscreen.h"
#include "../include/error.h"

# if 0 // def _CAANOO_
# define ROW_OFFSET 1
# else
# define ROW_OFFSET 0
# endif

void process_TS_Event(int x, int y);
//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
static void exit_func()
{
#ifdef __arm__
    string text = "\nSyncing SD card, don't power off...";
    cmd_cr();
    display_string(text.data(), text.size());
    show_cursor();
    sync();
    text = "done.";
    display_string(text.data(), text.size());
    show_cursor();
    sleep(1);
#endif
    SDL_Quit();
    close(master);
#ifdef __arm__
    fflush(stdout);
    fflush(stderr);
    //chdir("/usr/gp2x");
    //execl("/usr/gp2x/gp2xmenu","/usr/gp2x/gp2xmenu", NULL);
#endif
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
static void sigchld_handler (int signum)
{
    signal(SIGCHLD,SIG_IGN);    // don't recurse
    exit(0);                // child quit or detached from us
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
static void init()
{
    atexit(exit_func);

#ifdef __arm__
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0)
    {
        fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
        exit(1);
    }
#else
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
        exit(1);
    }
#endif

    screen = SDL_SetVideoMode(GP2X_WIDTH, GP2X_HEIGHT, 16, SDL_SWSURFACE);
    if (screen == NULL)
    {
        printf("Unable to set %dx%d video: %s\n", GP2X_WIDTH, GP2X_HEIGHT, SDL_GetError());
        exit(1);
    }

#ifdef __arm__
    joystick = SDL_JoystickOpen(0);
    if (joystick == NULL)
    {
        printf("Unable to open Joystick 0: %s\n", SDL_GetError());
    }
    SDL_ShowCursor(SDL_DISABLE);
#endif
#ifdef _CAANOO_
    SDL_EnableUNICODE(1);
#endif

    sterm = SDL_CreateRGBSurface(SDL_SWSURFACE, GP2X_CLIENT_WIDTH, GP2X_CLIENT_HEIGHT, 16, R, G, B, 0);
    if (sterm == NULL)
    {
        printf("Unable to create %dx%d surface: %s\n", GP2X_CLIENT_WIDTH, GP2X_CLIENT_HEIGHT, SDL_GetError());
        exit(1);
    }

    struct winsize winsize;
    winsize.ws_row = _clientHeight;
    winsize.ws_col = _clientWidth;
    winsize.ws_xpixel = 0;
    winsize.ws_ypixel = 0;
    signal(SIGCHLD,SIG_IGN); // XXX: should use sigaction for portability
    pid_t pid = forkpty(&master, NULL, NULL, &winsize);
    if(pid == -1)
    {
        perror("Unable to forkpty");
        exit(1);
    }

    if(pid == 0)                // child
    {
        char *bash_args[2 + EXTRA_ARGC];
        char *bash_env[3] = {"HOME=/mnt/sd", "TERM=linux", NULL};

        bash_args[0] = "/bin/ash";
        for (int i = 0; i < EXTRA_ARGC; ++i) {
          bash_args[1+i] = EXTRA_ARGV[i];
        }
        bash_args[1 + EXTRA_ARGC] = NULL;

        execve(bash_args[0], bash_args, bash_env);
        perror("Unable to execve\n");
        exit(1);
    }

    signal(SIGCHLD,sigchld_handler); // catch child process termination
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
void pos_adj(int delta)
{
    cur_pos += delta;
    int row = cur_pos / _clientWidth;
    hide_cursor();
    if(row >= _clientHeight)
    {
// ZX: TO_BE_DONE !
        // scroll up N lines
        int N = row - (_clientHeight) + 1;
        cur_pos -= N * _clientWidth;

        if(N > _clientHeight)
            N = _clientHeight;

        lock_surface(sterm);
        char *pixels = (char *)sterm->pixels;
        int pitch = sterm->pitch;
        memmove(pixels,
                pixels + pitch * N * FONT_HEIGHT,
                pitch * (_clientHeight-N) * FONT_HEIGHT);
        unlock_surface(sterm);
        // clear new lines
        SDL_Rect target;
        target.x = 0;
        target.y = GP2X_CLIENT_HEIGHT - N*FONT_HEIGHT;
        target.w = GP2X_CLIENT_WIDTH;
        target.h = N*FONT_HEIGHT;
        SDL_FillRect(sterm, &target, bg_color);

        SDL_BlitSurface(bg, NULL, screen, NULL);
        SDL_Rect blt_src;
        SDL_Rect blt_tgt;
        blt_src.x = 0;
        blt_src.y = 0;
        blt_src.w = GP2X_CLIENT_WIDTH;
        blt_src.h = GP2X_CLIENT_HEIGHT;
        blt_tgt.x = GP2X_CLIENT_TOP_X;
        blt_tgt.y = GP2X_CLIENT_TOP_Y;
        blt_tgt.w = blt_src.w;
        blt_tgt.h = blt_src.h;
        SDL_BlitSurface(sterm, &blt_src, screen, &blt_tgt);
        SDL_UpdateRect(screen, blt_tgt.x, blt_tgt.y, blt_tgt.w, blt_tgt.h);
    }

    if(cur_pos < 0)
        cur_pos = 0;

    show_cursor();
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
static void show_cursor()
{
    if(last_pos == cur_pos)
        return;

    hide_cursor();
    int row = cur_pos / _screenWidth + ROW_OFFSET;
    int col = cur_pos % _screenWidth;

    SDL_Rect target;
    target.x = FONT_WIDTH * col;
    target.y = row * FONT_HEIGHT + FONT_HEIGHT - 1;
    target.w = FONT_WIDTH;
    target.h = 1;
    target.x += GP2X_CLIENT_TOP_X;
    target.y += GP2X_CLIENT_TOP_Y;
    SDL_BlitSurface(bg, &target, screen, &target);
    lock_surface(screen);
    char *pixels = (char *)screen->pixels;
    int pitch = screen->pitch;
    int x = FONT_WIDTH * col;
    int y = row * FONT_HEIGHT + FONT_HEIGHT - 1;
    x += GP2X_CLIENT_TOP_X;
    y += GP2X_CLIENT_TOP_Y;
    short *base = (short *)(pixels + pitch * y + x * 2);
    for(int i=0; i<FONT_WIDTH; i++)
        base[i] = cursor_color;
    unlock_surface(screen);
    SDL_UpdateRect(screen, target.x, target.y, FONT_WIDTH, 1);

    last_pos = cur_pos;
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
static void hide_cursor()
{
    if(last_pos >= 0)
    {
        int row = last_pos / _screenWidth + ROW_OFFSET;
        int col = last_pos % _screenWidth;

        SDL_Rect target;
        SDL_Rect source;
        source.x = FONT_WIDTH * col;
        source.y = row * FONT_HEIGHT + FONT_HEIGHT - 1;
        source.w = FONT_WIDTH;
        source.h = 1;
        target.x = source.x + GP2X_CLIENT_TOP_X;
        target.y = source.y + GP2X_CLIENT_TOP_Y;
        target.w = source.w;
        target.h = source.h;
        SDL_BlitSurface(bg, &target, screen, &target);
        SDL_BlitSurface(sterm, &source, screen, &target);
        SDL_UpdateRect(screen, target.x, target.y, FONT_WIDTH, 1);
        last_pos = -1;
    }
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
static void draw_char( unsigned char c,
        bool inverse=false )
{
    int row = cur_pos / _screenWidth + ROW_OFFSET;
    int col = cur_pos % _screenWidth;
    draw_char(screen, bg, sterm, row, col, c, bg_color, fg_color, inverse);
}

void cmd_up()                   // Cursor up one line
{
    pos_adj(-_screenWidth);
}

void cmd_do()                   // Cursor down one line
{
    pos_adj(_screenWidth);
}

void cmd_nd()                   // Cursor right one character
{
    pos_adj(1);
}

void cmd_cr()                   // Carriage return
{
    cur_pos = cur_pos/_screenWidth*_screenWidth;
}

void cmd_ta()                   // move to next hardware tab
{
    int col = cur_pos % _screenWidth;
    pos_adj((col+7)/8*8-col);
}

void cmd_ho()                   // Cursor home
{
    cur_pos = 0;
}

void cmd_cd()                   // Clear to end of screen
{
    int row = cur_pos / _screenWidth + ROW_OFFSET;
    int col = cur_pos % _screenWidth;
    for(int i=col; i<_screenWidth; i++) // clear to the end of line
        clear_char(screen, bg, sterm, row, i, bg_color);
    for(int i=row+1; i<_clientHeight; i++) // clear other lines
        for(int j=0; j<_screenWidth; j++)
            clear_char(screen, bg, sterm, i, j, bg_color);
}

void cmd_ce()                   // Clear to end of line
{
    int row = cur_pos / _screenWidth + ROW_OFFSET;
    int col = cur_pos % _screenWidth;
    for(int i=col; i<_screenWidth; i++)
        clear_char(screen, bg, sterm, row, i, bg_color);
}

void cmd_se()                   // End standout mode
{
}

void cmd_cs(int arg1, int arg2) // Scroll region from line %1 to %2
{
}

void cmd_mb() { blinking_mode = true; }
void cmd_mh() { halfbright_mode = true; }
void cmd_md() { bold_mode = true; }
void cmd_mr() { reverse_mode = true; }
void cmd_mk() { dark_mode = true; }

void cmd_DO(int arg1)           // Cursor down #1 lines
{
    pos_adj(_screenWidth * arg1);
}

void cmd_UP(int arg1)           // Cursor up %1 lines
{
    pos_adj(-_screenWidth * arg1);
}

void cmd_LE(int arg1)           // Cursor left %1 characters
{
    pos_adj(-arg1);
}

void cmd_RI(int arg1)           // Cursor right %1 characters
{
    pos_adj(arg1);
}

void cmd_cm(int arg1, int arg2) // Cursor move to row %1 and column %2
    // (on screen)
{
    cur_pos = (arg1 - 1) * _screenWidth + (arg2 - 1);
    pos_adj(0);
}

void cmd_ks()                   // Turn keypad on
{
}

void cmd_us()                   // Start underlining
{
}

void cmd_ue()                   // End underlining
{
}

void cmd_cv(int arg1)        // Move cursor vertically only to line %1
{
    int col = cur_pos % _screenWidth;
    cur_pos = (arg1 - 1) * _screenWidth + col;
    pos_adj(0);
}

void cmd_al()                   // Insert one line
{
    int row = cur_pos / _screenWidth + ROW_OFFSET;

    hide_cursor();

    lock_surface(sterm);

    char *pixels = (char *)sterm->pixels;
    int pitch = sterm->pitch;
    memmove(pixels + pitch * (row+1) * FONT_HEIGHT,
            pixels + pitch * row * FONT_HEIGHT,
            pitch * (_clientHeight - row) * FONT_HEIGHT);
    // clear the new line
    SDL_Rect source;
    SDL_Rect target;
    source.x = 0;
    source.y = row * FONT_HEIGHT;
    source.w = GP2X_CLIENT_WIDTH;
    source.h = FONT_HEIGHT;
    target.x = source.x + GP2X_CLIENT_TOP_X;
    target.y = source.y + GP2X_CLIENT_TOP_Y;
    target.w = source.w;
    target.h = source.h;
    SDL_FillRect(sterm, &source, bg_color);

    unlock_surface(sterm);

    SDL_BlitSurface(bg, &target, screen, &target);
    SDL_BlitSurface(sterm, &source, screen, &target);
    SDL_UpdateRect(screen, target.x, target.y, target.w, target.h );

    show_cursor();
}

void cmd_ei()                   // End insert mode
{
}

void cmd_im()                   // Begin insert mode
{
}

void cmd_S2()                   // ?
{
}

void cmd_S3()                   // ?
{
}

void cmd_le()   // Cursor left one character
{
    pos_adj(-1);
}

int saved_cursor_position;
void cmd_sc()   // Save cursor position
{
    saved_cursor_position = cur_pos;
}

void cmd_rc()   // Restore saved cursor position
{
    cur_pos = saved_cursor_position;
}

void cmd_ch(int arg1)   // Move cursor horizontally only to column %1
{
    cur_pos = cur_pos/_screenWidth * _screenWidth + (arg1-1);
    pos_adj(0);
}

void cmd_DC(int arg1) // Delete %1 characters
{
    hide_cursor();
    lock_surface(sterm);
    int row = cur_pos/_screenWidth + ROW_OFFSET;
    int col = cur_pos%_screenWidth;
    if(arg1 > _screenWidth - col) arg1 = _screenWidth - col;
    char *pixels = (char *)sterm->pixels;
    int pitch = sterm->pitch;

    // move from col+arg1 to col, length SCREEN_WIDTH-col-arg1
    int x0 = FONT_WIDTH * col;
    int x1 = FONT_WIDTH * (col + arg1);
    int len = FONT_WIDTH * (_screenWidth-col-arg1);
    int y = FONT_HEIGHT * row;
    for(int i=0; i<FONT_HEIGHT; i++)
    {
        memmove(pixels + pitch * (y+i) + x0*2,
                pixels + pitch * (y+i) + x1*2,
                len*2);
    }

    // clear new space
    SDL_Rect target;
    target.x = (_screenWidth-arg1) * FONT_WIDTH;
    target.y = y;
    target.w = arg1 * FONT_WIDTH;
    target.h = FONT_HEIGHT;
    SDL_FillRect(sterm, &target, bg_color);
    unlock_surface(sterm);

    SDL_Rect update_rect_src;
    SDL_Rect update_rect_tgt;
    update_rect_src.x = x0;
    update_rect_src.y = row*FONT_HEIGHT;
    update_rect_src.w = GP2X_WIDTH - x0;
    update_rect_src.h = FONT_HEIGHT;
    update_rect_tgt.x = update_rect_src.x + GP2X_CLIENT_TOP_X;
    update_rect_tgt.y = update_rect_src.y + GP2X_CLIENT_TOP_Y;
    update_rect_tgt.w = update_rect_src.w;
    update_rect_tgt.h = update_rect_src.h;

    SDL_BlitSurface(bg, &update_rect_tgt, screen, &update_rect_tgt);
    SDL_BlitSurface(sterm, &update_rect_src, screen, &update_rect_tgt);
    SDL_UpdateRect(screen, update_rect_tgt.x, update_rect_tgt.y,
            update_rect_tgt.w, update_rect_tgt.h);
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
void cmd_IC(int arg1) // Insert %1 characters
{
    hide_cursor();
    lock_surface(sterm);
    int row = cur_pos/_screenWidth + ROW_OFFSET;
    int col = cur_pos%_screenWidth;
    if(arg1 > _screenWidth - col) arg1 = _screenWidth - col;
    char *pixels = (char *)sterm->pixels;
    int pitch = sterm->pitch;

    // move from col to col+arg1, length SCREEN_WIDTH-col-arg1
    int x0 = FONT_WIDTH * col;
    int x1 = FONT_WIDTH * (col + arg1);
    int len = FONT_WIDTH * (_screenWidth-col-arg1);
    int y = FONT_HEIGHT * row;
    for(int i=0;i<FONT_HEIGHT;i++)
    {
        memmove(pixels + pitch * (y+i) + x1*2,
                pixels + pitch * (y+i) + x0*2,
                len*2);
    }

    // clear new space
    SDL_Rect target;
    target.x = col * FONT_WIDTH;
    target.y = y;
    target.w = arg1 * FONT_WIDTH;
    target.h = FONT_HEIGHT;
    SDL_FillRect(sterm, &target, bg_color);
    unlock_surface(sterm);

    SDL_Rect update_rect_src;
    SDL_Rect update_rect_tgt;
    update_rect_src.x = x0;
    update_rect_src.y = row*FONT_HEIGHT;
    update_rect_src.w = GP2X_WIDTH - x0;
    update_rect_src.h = FONT_HEIGHT;
    update_rect_tgt.x = update_rect_src.x + GP2X_CLIENT_TOP_X;
    update_rect_tgt.y = update_rect_src.y + GP2X_CLIENT_TOP_Y;
    update_rect_tgt.w = update_rect_src.w;
    update_rect_tgt.h = update_rect_src.h;

    SDL_BlitSurface(bg, &update_rect_tgt, screen, &update_rect_tgt);
    SDL_BlitSurface(sterm, &update_rect_src, screen, &update_rect_tgt);
    SDL_UpdateRect(screen, update_rect_tgt.x, update_rect_tgt.y,
            update_rect_tgt.w, update_rect_tgt.h);
}

void cmd_color(int a)
{
    if(a >= 30 && a <= 37)
        fg_color = colors[a-30];
    else if(a >= 40 && a <= 47)
        bg_color = colors[a-40];
    else if(a == 39)
        fg_color = def_fg_color;
    else if(a == 49)
        bg_color = def_bg_color;
    else if(a == 0)
    {
        // End all mode like so, us, mb, md and mr
        fg_color = def_fg_color;
        bg_color = def_bg_color;
        blinking_mode = false;
        halfbright_mode = false;
        bold_mode = false;
        reverse_mode = false;
    }
}

//----------------------------------------------------------------------------//
// Display N characters from BUF on the screen                                //
//----------------------------------------------------------------------------//
void display_string(const char *buf, int n)
{
    static bool esc_mode = false;

    hide_cursor();
    for(int i=0; i<n; i++)
    {
        unsigned char c = buf[i];
#ifdef __debug__
        printf("read: 0x%x(%c)\n", c, c);
#endif

        if(esc_mode)
        {
            esc_mode = process_esc_mode(c);
            continue;
        }

        switch(c)
        {
            case '\n':          // ^J
                cmd_do();
                break;
            case '\r':          // ^M
                cmd_cr();
                break;
            case '\t':          // ^I
                cmd_ta();
                break;
            case '\b':          // ^H
                cmd_le();
                break;
            case '\a':          // ^G
                break;
            case 0x1b:          // ESC
                esc_mode = true;
                break;
            case '\177':
                clear_char(screen, bg, sterm, (cur_pos-1) / _screenWidth, (cur_pos-1) % _screenWidth, bg_color);
            default:
                draw_char(c, reverse_mode);
                pos_adj(+1);
                break;
        }
    }
}

//----------------------------------------------------------------------------//
//                                                                                    //
//----------------------------------------------------------------------------//
void inline display_string(string str)
{
    display_string(str.data(), str.size());
}

//----------------------------------------------------------------------------//
// return true if still in esc mode                                           //
//----------------------------------------------------------------------------//
bool process_esc_mode(unsigned char c)
{
    static int state = INITIAL;
    static int arg1 = 0, arg2 = 0;

    switch(state)
    {
        case INITIAL:
            if(c == '[') { state = LBRACKET; return true; }
            if(c == '7') { cmd_sc(); goto done; }
            if(c == '8') { cmd_rc(); goto done; }
            break;
        case LBRACKET:
            if(c >= '0' && c <= '9')
            {
                state = IN_ARG1; arg1=c-'0'; return true;
            }
            if(c == 'A') { cmd_up(); goto done; }
            if(c == 'B') { cmd_do(); goto done; }
            if(c == 'C') { cmd_nd(); goto done; }
            if(c == 'H') { cmd_ho(); goto done; }
            if(c == 'K') { cmd_ce(); goto done; }
            if(c == 'J') { cmd_cd(); goto done; }
            if(c == 'L') { cmd_al(); goto done; }
            if(c == 'P') { cmd_DC(1); goto done; }
            if(c == '@') { cmd_IC(1); goto done; }
            if(c == 'm') { cmd_se(); goto done; }
            if(c == '?') { state = LBRACKET_Q; return true; }
            break;
        case LBRACKET_Q:
            if(c >= '0' && c <= '9')
            {
                state = IN_ARG1_Q; arg1=c-'0'; return true;
            }
            break;
        case IN_ARG1_Q:
            if(c >= '0' && c <= '9')
            {
                arg1 = arg1*10+(c-'0'); return true;
            }
            else if(c == 'h')
            {
                if(arg1 == 1) { cmd_ks(); goto done; }
                if(arg1 == 25) {
                    // :ve=\E[?25h\E[?0c
                    // :vi=\E[?25l\E[?1c
                    // :vs=\E[?25h\E[?8c
                    goto done;
                }
            }
            else if(c == 'l')
            {
                if(arg1 == 25)
                    goto done;
            }
            else if(c == 'c')
            {
                if(arg1 == 0 || arg1 == 1 || arg1 == 8)
                    goto done;
            }
            break;
        case IN_ARG1:
            if(c >= '0' && c <= '9')
            {
                arg1 = arg1*10+(c-'0'); return true;
            }
            else if(c == 'm')
            {
                if(arg1 == 5) { cmd_mb(); goto done; }
                if(arg1 == 2) { cmd_mh(); goto done; }
                if(arg1 == 1) { cmd_md(); goto done; }
                if(arg1 == 7) { cmd_mr(); goto done; }
                if(arg1 == 8) { cmd_mk(); goto done; }
                if(arg1 == 4) { cmd_us(); goto done; }
                if(arg1 == 11) { cmd_S2(); goto done; }
                if(arg1 == 10) { cmd_S3(); goto done; }
                if(arg1 == 24) { cmd_ue(); goto done; }
                if(arg1 >= 30 && arg1 <= 49 || arg1 == 0)
                {
                    cmd_color(arg1); goto done;
                }
            }
            else if(c == 'A')
            {
                cmd_UP(arg1); goto done;
            }
            else if(c == 'B')
            {
                cmd_DO(arg1); goto done;
            }
            else if(c == 'C')
            {
                cmd_RI(arg1); goto done;
            }
            else if(c == 'D')
            {
                cmd_LE(arg1); goto done;
            }
            else if(c == 'G')
            {
                cmd_ch(arg1); goto done;
            }
            else if(c == 'J')
            {
                if(arg1 == 2 || arg1 == 0) { cmd_cd(); goto done; }
            }
            else if(c == 'K')
            {
                if(arg1 == 0) { cmd_ce(); goto done; }
            }
            else if(c == 'P')
            {
                cmd_DC(arg1); goto done;
            }
            else if(c == '@')
            {
                cmd_IC(arg1); goto done;
            }
            else if(c == ';')
            {
                arg2 = 0; state = IN_ARG2; return true;
            }
            else if(c == 'd')
            {
                cmd_cv(arg1); goto done;
            }
            else if(c == 'l')
            {
                if(arg1 == 4) { cmd_ei(); goto done; }
            }
            else if(c == 'h')
            {
                if(arg1 == 4) { cmd_im(); goto done; }
            }
            break;
        case IN_ARG2:
            if(c >= '0' && c <= '9')
            {
                arg2 = arg2*10+(c-'0'); return true;
            }
            else if(c == 'r')
            {
                cmd_cs(arg1, arg2); goto done;
            }
            else if(c == 'H')
            {
                cmd_cm(arg1, arg2); goto done;
            }
            else if(c == 'm')
            {
                cmd_color(arg1);
                cmd_color(arg2);
                goto done;
            }
            break;
    }
    return false;
done:
#ifdef __debug__
    printf("------------------------------------------\n");
#endif
    state = INITIAL;
    return false;
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
void init_keyboard()
{
    if(stermKeyboard == NULL)
        return;

    bg = IMG_Load(BG_IMAGE);
    SDL_BlitSurface(bg, NULL, screen, NULL);
    SDL_UpdateRect(screen, 0, 0, 0, 0);

    StermSection *section;
    section = new StermSection(3, 2, false);

    section->AddButton(new StermButton(screen, 21, 0, new StermKey("" , "SHF"), 0xFFFFFF, 0), 0, 0);
    section->AddButton(new StermButton(screen, 22, 0, new StermKey("", "CTR"), 0xFFFFFF, 0), 1, 0);
    section->AddButton(new StermButton(screen, 23, 0, new StermKey("", "ALT"), 0xFFFFFF, 0), 2, 0);
    section->AddButton(shiftButton = new StermButton(screen, 21, 3, new StermKey("", " "), 0, G), 0, 1);
    section->AddButton(ctrlButton = new StermButton(screen, 22, 3, new StermKey("", " "), 0, G), 1, 1);
    section->AddButton(altButton = new StermButton(screen, 23, 3, new StermKey("", " "), 0, G), 2, 1);

    stermKeyboard->AddSection(0, section);
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
string send_buf;    // the buffer we are sending to the pty

void process_direction(int type, bool js_down)
{
    switch (type)
    {
        case GP2X_BUTTON_RIGHT:
            if(js_down)
                send_buf += "\x1b[C";
            else if(vkeyboard)
                stermKeyboard->GetCurrentSection()->MoveRight();
            break;
        case GP2X_BUTTON_LEFT:
            if(js_down)
                send_buf += "\x1b[D";
            else if(vkeyboard)
                stermKeyboard->GetCurrentSection()->MoveLeft();
            break;
        case GP2X_BUTTON_UP:
            if(js_down)
                send_buf += "\x1b[A";
            else if(vkeyboard)
                stermKeyboard->GetCurrentSection()->MoveUp();
            break;
        case GP2X_BUTTON_DOWN:
            if(js_down)
                send_buf += "\x1b[B";
            else if(vkeyboard)
                stermKeyboard->GetCurrentSection()->MoveDown();
            break;
        default:
            return;
    }
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
void insert_key(string str)
{
    send_buf += str;
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
void insert_key(char c)
{
    if(!usbkeyboard)
    {    
        if(ctrl_down && c >= 'a' && c <= 'z')
            c = c - 'a' + 1;        // ^A - ^Z
        else if(shift_down){
            if(c >= 'a' && c <= 'z')
                c = c - 'a' + 'A';      // A - Z
            else
            {
                char val = cm[c];
                if(val)
                    send_buf += cm[c];
                else 
                    send_buf += c;
                return;
            }
        }
        else if(alt_down && c >= 'A' && c <= 'Z')
            c = c - 'A' + 'a';      // a - z
        if(alt_down)
            c = c | 0x80;           // set 8th bit*/
    }

    send_buf += c;
}

bool switch_keys;

int revert_button(int butt) {
    switch(butt) {
        case GP2X_BUTTON_A: return GP2X_BUTTON_LEFT;
        case GP2X_BUTTON_B: return GP2X_BUTTON_RIGHT;
        case GP2X_BUTTON_X: return GP2X_BUTTON_DOWN;
        case GP2X_BUTTON_Y: return GP2X_BUTTON_UP;
        case GP2X_BUTTON_UP: return GP2X_BUTTON_Y;
        case GP2X_BUTTON_DOWN: return GP2X_BUTTON_X;
        case GP2X_BUTTON_LEFT: return GP2X_BUTTON_A;
        case GP2X_BUTTON_RIGHT: return GP2X_BUTTON_B;
        default: return butt;
    }
}


#ifdef _CAANOO_
enum
{
     BTN_A = 0,     //       A /             1
     BTN_X = 1,     //       X /             2
     BTN_B = 2,     //       B /             3
     BTN_Y = 3,     //       Y /             4
     BTN_L = 4,     //       L /         5, L1
     BTN_R = 5,     //       R /         6, L2
     BTN_HOME = 6,  //    Home /         7, R1
     BTN_HOLD = 7,  //    Hold /         8, R2
     BTN_HELP1 = 8, //  Help I /        Select
     BTN_HELP2 = 9, // Help II /         Start
     BTN_TACT = 10, //    Tact / L Thumb Stick
     BTN_UP = 11, 
     BTN_DOWN = 12,
     BTN_LEFT = 13,
     BTN_RIGHT = 14,
     // DIAGONALS
     BTN_UP_LEFT = 15,
     BTN_UP_RIGHT = 16,
     BTN_DOWN_LEFT = 17,
     BTN_DOWN_RIGHT = 18
};

void Caanoo_PushAnalogEvent(int btn, int pressed)
{
    SDL_Event event;
# if 0 //ZX: DEBUG
fprintf(stdout, "Caanoo_PushAnalogEvent %d %d %d\n", SDL_GetTicks(), btn, pressed);
# endif
    event.type = (pressed)?SDL_JOYBUTTONDOWN:SDL_JOYBUTTONUP;
    event.jbutton.button = btn;
    event.jbutton.state = (pressed)?SDL_PRESSED:SDL_RELEASED;
    event.jbutton.which = 0;
        
    SDL_PushEvent(&event);      
}

extern SDL_Joystick* joystick;

void Caanoo_UpdateAnalog(void)
{
    static int buttonsPrev = 0;
    int buttonsNow = 0;
        
    SDL_JoystickUpdate();

    int joy_x = SDL_JoystickGetAxis( joystick, 0 );
    int joy_y = SDL_JoystickGetAxis( joystick, 1 );
                
    if (joy_x < -16384) buttonsNow |= (1 << BTN_LEFT);
    if (joy_x >  16384) buttonsNow |= (1 << BTN_RIGHT);
    if (joy_y < -16384) buttonsNow |= (1 << BTN_UP);
    if (joy_y >  16384) buttonsNow |= (1 << BTN_DOWN);
    
    // DIAGONALS
    if (joy_x > 11585 && joy_y < -11585)
      buttonsNow |= (1 << BTN_UP_RIGHT);
    if (joy_x > 11585 && joy_y > 11585)
      buttonsNow |= (1 << BTN_DOWN_RIGHT);
    if (joy_x < -11585 && joy_y > 11585)
      buttonsNow |= (1 << BTN_DOWN_LEFT);
    if (joy_x < -11585 && joy_y < -11585)
      buttonsNow |= (1 << BTN_UP_LEFT);

    if (buttonsNow != buttonsPrev)
    {
        if ((buttonsNow & (1 << BTN_LEFT)) != (buttonsPrev & (1 << BTN_LEFT)))
        {
                Caanoo_PushAnalogEvent(BTN_LEFT, (buttonsNow & (1 << BTN_LEFT)));
        }
        if ((buttonsNow & (1 << BTN_RIGHT)) != (buttonsPrev & (1 << BTN_RIGHT)))
        {
                Caanoo_PushAnalogEvent(BTN_RIGHT, (buttonsNow & (1 << BTN_RIGHT)));
        }
        if ((buttonsNow & (1 << BTN_UP)) != (buttonsPrev & (1 << BTN_UP)))
        {
                Caanoo_PushAnalogEvent(BTN_UP, (buttonsNow & (1 << BTN_UP)));
        }
        if ((buttonsNow & (1 << BTN_DOWN)) != (buttonsPrev & (1 << BTN_DOWN)))
        {
                Caanoo_PushAnalogEvent(BTN_DOWN, (buttonsNow & (1 << BTN_DOWN)));
        }
      // DIAGONALS
      if ((buttonsNow & (1 << BTN_UP_RIGHT)) != (buttonsPrev & (1<< BTN_UP_RIGHT)))
        {
          Caanoo_PushAnalogEvent(BTN_UP_RIGHT, (buttonsNow & (1 << BTN_UP_RIGHT)));
        }
      if ((buttonsNow & (1 << BTN_DOWN_RIGHT)) != (buttonsPrev & (1<< BTN_DOWN_RIGHT)))
        {
          Caanoo_PushAnalogEvent(BTN_DOWN_RIGHT, (buttonsNow & (1 << BTN_DOWN_RIGHT)));
        }
      if ((buttonsNow & (1 << BTN_DOWN_LEFT)) != (buttonsPrev & (1<< BTN_DOWN_LEFT)))
        {
          Caanoo_PushAnalogEvent(BTN_DOWN_LEFT, (buttonsNow & (1 << BTN_DOWN_LEFT)));
        }
      if ((buttonsNow & (1 << BTN_UP_LEFT)) != (buttonsPrev & (1<< BTN_UP_LEFT)))
        {
          Caanoo_PushAnalogEvent(BTN_UP_LEFT, (buttonsNow & (1 << BTN_UP_LEFT)));
        }
    }
    buttonsPrev = buttonsNow;
}
#endif


//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
SDL_Event last_event;
void poll_event(SDL_Event &event)
{
    switch (event.type)
    {
        case SDL_JOYBUTTONDOWN:
            int butt;
            if(switch_keys)
                butt = revert_button(event.jbutton.button);
            else
                butt = event.jbutton.button;
            switch(butt)
            {
                case GP2X_BUTTON_START:
                    goto quit;
                case GP2X_BUTTON_L:
                    if(js_down)
                    {
                        sync();
                        signal(SIGCHLD, SIG_IGN);
                        SDL_Quit();
                        close(master);
                        fflush(stdout);
                        fflush(stderr);

                        execl("selector","selector","selector.conf",NULL);
                        exit(0);
                    }
                    else if(vkeyboard)
                        stermKeyboard->MoveLeft();
                    break;
                case GP2X_BUTTON_R:
                    if(js_down)
                    {
                        if(caps_lock)
                        {
                            caps_lock = false;
                            shift_down = false;
                            if(vkeyboard)
                                shiftButton->UnSelect();
                        }
                        else
                        {
                            caps_lock = true;
                            shift_down = true;
                            if(vkeyboard)
                                shiftButton->Select();
                        }
                    }
                    else if(vkeyboard)
                        stermKeyboard->MoveRight();
                    break;
                case GP2X_BUTTON_VOLDOWN:
                    if(js_down)
                        goto alt;
                    else
                    {
                        if(ctrl_down)
                            goto ctrl_up;
                        else
                        {
                            ctrl_down = true;
                            if(vkeyboard)
                                ctrlButton->Select();
                        }
                        break;
                    }
                case GP2X_BUTTON_VOLUP:
                    if(js_down)
                        goto esc;
                    else
                        if(shift_down)
                        {
                            if(!caps_lock)
                                goto shift_up;
                        }
                        else
                        {
                            shift_down = true;
                            if(vkeyboard)
                                shiftButton->Select();
                        }
                    break;
                case GP2X_BUTTON_SELECT:
                    goto js;
                case GP2X_BUTTON_CLICK:
                    if(js_down)
                    {
                        lock_surface(screen);
                        string fileName = GenerateUniqueFileName("sterm") + ".bmp";
                        SDL_SaveBMP(screen, fileName.data());
                        display_string("Saved screenshot " + fileName);
                        insert_key('\n');
                        unlock_surface(screen);
                    }
                    else
                        goto tab;
                    break;
                case GP2X_BUTTON_RIGHT:
                case GP2X_BUTTON_LEFT:
                case GP2X_BUTTON_UP:
                case GP2X_BUTTON_DOWN:
                    process_direction(butt, js_down);
                    break;
                case GP2X_BUTTON_A:
                    goto insert;
                case GP2X_BUTTON_B:
                    goto enter;
                case GP2X_BUTTON_X:
                    goto back;
                case GP2X_BUTTON_Y:
space:                  send_buf += ' ';
                        break;
            }
            break;
        case SDL_JOYBUTTONUP:
            switch(event.jbutton.button)
            {
                /*case GP2X_BUTTON_L:
                  goto alt_up;*/
                case GP2X_BUTTON_SELECT:
                    goto js_up;
            }
            break;
        case SDL_KEYDOWN:                
            switch(event.key.keysym.sym)
            {
                case SDLK_RIGHT:
                    process_direction(GP2X_BUTTON_RIGHT, !js_down);
                    break;
                case SDLK_LEFT:
                    process_direction(GP2X_BUTTON_LEFT, !js_down);
                    break;
                case SDLK_UP:
                    process_direction(GP2X_BUTTON_UP, !js_down);
                    break;
                case SDLK_DOWN:
                    process_direction(GP2X_BUTTON_DOWN, !js_down);
                    break;
                case SDLK_INSERT:
insert:                 if(shift_down)
                        {
                            if(vkeyboard)
                                insert_key(stermKeyboard->Get_Shift_Val());
                            goto shift_up;
                        }
                        else if(ctrl_down)
                        {
                            if(vkeyboard)
                                insert_key(stermKeyboard->Get_Ctrl_Val());
                            goto ctrl_up;
                        }
                        else if(vkeyboard)
                            insert_key(stermKeyboard->Get_Val());
                        break;
                case SDLK_BACKSPACE:
back:                   send_buf += '\177';
                        break;
                case SDLK_RETURN:
enter:                  send_buf.append("\r");
                        break;
                case SDLK_LALT:
                case SDLK_RALT:
alt:                    if(alt_down)
                            goto alt_up;
                        else
                        {
                            alt_down = true;
                            if(vkeyboard)
                                altButton->Select();
                        }
                        break;
                case SDLK_LCTRL:
                        //case SDLK_RCTRL:
ctrl:                   /*if(ctrl_down)
                          goto ctrl_up;
                          else
                          {*/
                        ctrl_down = true;
                        if(vkeyboard)
                            ctrlButton->Select();
                        //}
                        break;
                case SDLK_LSHIFT:
                case SDLK_RSHIFT:
shift:                  /*if(shift_down)
                          {
                          if(!caps_lock)
                          goto shift_up;
                          }
                          else
                          {*/
                        shift_down = true;
                        if(vkeyboard)
                            shiftButton->Select();
                        //}
                        break;
                case SDLK_CAPSLOCK:
                        if(caps_lock)
                        {
                            caps_lock = false;
                            goto shift_up;
                        }
                        else
                        {
                            shift_down = true;
                            caps_lock = true;
                            if(vkeyboard)
                                shiftButton->Select();
                        }
                        break;
                case SDLK_RCTRL:
js:                     js_down = true;
                        break;
                case SDLK_ESCAPE:
esc:                    send_buf += (char)0x1b;
                        break;
                case SDLK_TAB:
tab:                    send_buf += '\t';
                        break;
                default:     
                        char key;                   
                        if(event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0 ){
                            // insert key verbatim if it is in ASCII range
                            key = (char)event.key.keysym.unicode;
                        }
                        else{
                            // otherwise insert the sym
                            key = event.key.keysym.sym;
                        }
                        insert_key(key);
                        break;
            }
            break;
        case SDL_KEYUP:
            last_event.type = 0;
            switch(event.key.keysym.sym)
            {
                case SDLK_LALT:
alt_up:                 alt_down = false;
                        if(vkeyboard)
                            altButton->UnSelect();
                        break;
                case SDLK_LCTRL:
ctrl_up:                ctrl_down = false;
                        if(vkeyboard)
                            ctrlButton->UnSelect();
                        break;
                case SDLK_LSHIFT:
                case SDLK_RSHIFT:
                case SDLK_CAPSLOCK:
shift_up:               if(!caps_lock)
                        {
                            shift_down = false;
                            if(vkeyboard)
                                shiftButton->UnSelect();
                        }
                        break;
                case SDLK_RCTRL:
js_up:                  js_down = false;
                        break;			
            }
            break;
        case SDL_QUIT:
quit:           signal(SIGCHLD, SIG_IGN);
                exit(0);
    }
#ifdef _CAANOO_
    //process Caanoo's touchscreen
    if(event.button.button == SDL_BUTTON_LEFT)
    {
      if(event.type == SDL_MOUSEBUTTONDOWN)
      {
        process_TS_Event(event.button.x, event.button.y);
      }
    }
#endif
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
void process_SDL_Event()
{
    SDL_Event event;
    //bool no_event = true;

    while (1)
    {
#ifdef _CAANOO_
       Caanoo_UpdateAnalog();
#endif
       if (! SDL_PollEvent(&event)) break;

       last_event = event;
       //no_event = false;
       no_event2 = REPEAT_COUNTER2;
       poll_event(event);
    }

    if(!(--no_event2))          // check for repeat
    {
        no_event2 = REPEAT_COUNTER2/2;
        poll_event(last_event);
    }
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
void process_TS_Event(int x, int y) {
    stermKeyboard->Select(x/BIG_FONT_WIDTH,y/BIG_FONT_HEIGHT);
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
int load_config()
{
    SI_Error rc;
    // load the config file
    CSimpleIni config(false, true, true);
    rc = config.LoadFile(CONFIG_FILE);
    if (rc < 0) {
        printf("Failed to open config file.\n");
        return rc;
    }

    const char *keyVal = NULL;

    keyVal = config.GetValue("GENERAL", "vkeyboard", "true");
    if(!strcmp(keyVal, "true"))
    {
        vkeyboard = true;
        _gp2xClientHeight = 210;
    }
    else
    {
        vkeyboard = false;
        _gp2xClientHeight = 240;
    }

    keyVal = config.GetValue("GENERAL", "usbkeyboard", "auto");
    if(!strcmp(keyVal, "auto")) usbkeyboard = true;
    else                        usbkeyboard = false;

# ifdef _CAANOO_ //ZX:
    FONT_WIDTH = SMALL_FONT_WIDTH;
    FONT_HEIGHT = SMALL_FONT_HEIGHT;
    font = *small_font;
# else
    keyVal = config.GetValue("GENERAL", "term_size", "1");
    if(atoi(keyVal) == 2) {
       FONT_WIDTH = SMALL_FONT_WIDTH;
        FONT_HEIGHT = SMALL_FONT_HEIGHT;
        font = *small_font;
    } else {
        FONT_WIDTH = BIG_FONT_WIDTH;
        FONT_HEIGHT = BIG_FONT_HEIGHT;
        font = *big_font;
    }
# endif

    _gp2xClientHeight -= GP2X_CLIENT_TOP_Y;
    _gp2xClientWidth   = GP2X_WIDTH - GP2X_CLIENT_TOP_X;

    //ZX: _gp2xClientHeight should be a integer number of FONT_HEIGHT
    _gp2xClientHeight = (_gp2xClientHeight * FONT_HEIGHT + FONT_HEIGHT - 1 ) / FONT_HEIGHT;
    _gp2xClientWidth  = (_gp2xClientWidth  * FONT_WIDTH  + FONT_WIDTH  - 1 ) / FONT_WIDTH;

    _screenWidth  = GP2X_WIDTH/FONT_WIDTH;
    _screenHeight = GP2X_HEIGHT/FONT_HEIGHT;
    _clientWidth  = _screenWidth;

    if(vkeyboard)
        _clientHeight = GP2X_CLIENT_HEIGHT/FONT_HEIGHT;
    else
        _clientHeight = _screenHeight;

    switch_keys = false;
    keyVal = config.GetValue("GENERAL", "switch_keys", "false");
    if(strcmp(keyVal,"true") == 0) switch_keys = true;

    init();

    keyVal = config.GetValue("COLORS", "background"/*, 0, &bHasMulti*/);
    if(keyVal)
        BG_IMAGE = keyVal;

    bg = IMG_Load(BG_IMAGE);
    SDL_BlitSurface(bg, NULL, screen, NULL);
    SDL_UpdateRect(screen, 0, 0, 0, 0);

    //colors
    keyVal = config.GetValue("COLORS", "bgcolor"/*, 0, &bHasMulti*/);
    if(keyVal)
        bg_color = def_bg_color = atoi(keyVal);
    SDL_SetColorKey(sterm, SDL_SRCCOLORKEY /*| SDL_RLEACCEL*/, bg_color);//transparent color
    SDL_FillRect(sterm, NULL, bg_color);

    keyVal = config.GetValue("COLORS", "fgcolor");
    if(keyVal)
        fg_color = def_fg_color = atoi(keyVal);

    keyVal = config.GetValue("COLORS", "cursorcolor");
    if(keyVal)
        cursor_color = atoi(keyVal);

    if(vkeyboard)
    {
        //sections
        keyVal = config.GetValue("SECTIONS", "count");
        if(keyVal)
        {
            if(stermKeyboard != NULL)
                delete stermKeyboard;

            stermKeyboard = new StermKeyboard(atoi(keyVal));
        }
        else
            return -2;

        init_keyboard();

        keyVal = config.GetValue("COLORS", "shiftbgcolor");
        if(keyVal)
            shiftButton->Set_Bg_Color(atoi(keyVal));

        keyVal = config.GetValue("COLORS", "shiftfgcolor");
        if(keyVal)
            shiftButton->Set_Fg_Color(atoi(keyVal));

        keyVal = config.GetValue("COLORS", "ctrlbgcolor");
        if(keyVal)
            ctrlButton->Set_Bg_Color(atoi(keyVal));

        keyVal = config.GetValue("COLORS", "ctrlfgcolor");
        if(keyVal)
            ctrlButton->Set_Fg_Color(atoi(keyVal));

        keyVal = config.GetValue("COLORS", "altbgcolor");
        if(keyVal)
            altButton->Set_Bg_Color(atoi(keyVal));

        keyVal = config.GetValue("COLORS", "altfgcolor");
        if(keyVal)
            altButton->Set_Fg_Color(atoi(keyVal));
    }
    //usb keyboard configuration
    CSimpleIni::TNamesDepend keys;
    const char *keyName = NULL;
    if (config.GetAllValues("USBKEYBOARD", "key", keys)) {
        CSimpleIni::TNamesDepend::const_iterator keys_iterator = keys.begin();
        for (; keys_iterator != keys.end(); ++keys_iterator) {
            keyName = *keys_iterator;
            keyVal = config.GetValue(keyName, "shiftvalue");
            if(keyVal)
                cm.insert(charValType(keyName[0], keyVal[0]));
            else
                cm.insert(charValType(keyName[0], keyName[0]));
        }
    }

    if(vkeyboard)
    {
        //on-screen keyboard configuration
        CSimpleIni::TNamesDepend sections, buttons;
        const char *buttonName = NULL, *sectionName = NULL;

        if (config.GetAllValues("SECTIONS", "section", sections)) {
            CSimpleIni::TNamesDepend::const_iterator sections_iterator = sections.begin();
            for (; sections_iterator != sections.end(); ++sections_iterator) {
                sectionName = *sections_iterator;
                int section_id, section_rows, section_cols;

                keyVal = config.GetValue(sectionName, "sectionid");
                if(keyVal)
                    section_id = atoi(keyVal);
                else // if we don't have an id do not parse this section
                    continue;

                keyVal = config.GetValue(sectionName, "rows");
                if(keyVal)
                    section_rows = atoi(keyVal);
                else // If we don't know how many rows we have, do not parse this section
                    continue;

                keyVal = config.GetValue(sectionName, "cols");
                if(keyVal)
                    section_cols = atoi(keyVal);
                else // If we don't know how many cols we have, do not parse this section
                    continue;

                StermSection *section = new StermSection(section_rows, section_cols);

                if (config.GetAllValues(sectionName, "button", buttons)) {
                    CSimpleIni::TNamesDepend::const_iterator i = buttons.begin();
                    for (; i != buttons.end(); ++i) {
                        buttonName = *i;
                        int row,col,bgcolor,fgcolor,sectionrow,sectioncol;
                        string value,label,shift_value,shift_label,ctrl_value,ctrl_label;

                        keyVal = config.GetValue(buttonName, "row");
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            row = atoi(keyVal);
                        else continue;

                        keyVal = config.GetValue(buttonName, "col");
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            col = atoi(keyVal);
                        else continue;

                        keyVal = config.GetValue(buttonName, "sectionrow");
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            sectionrow = atoi(keyVal);
                        else continue;

                        keyVal = config.GetValue(buttonName, "sectioncol");
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            sectioncol = atoi(keyVal);
                        else continue;

                        keyVal = config.GetValue(buttonName, "bgcolor");
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            bgcolor = atoi(keyVal);
                        else continue;

                        keyVal = config.GetValue(buttonName, "fgcolor");
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            fgcolor = atoi(keyVal);
                        else continue;

                        keyVal = StermValueParser::Parse((char *)config.GetValue(buttonName, "value"));
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            value = keyVal;
                        else continue;

                        keyVal = config.GetValue(buttonName, "label");
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            label = keyVal;
                        else continue;

                        keyVal = StermValueParser::Parse((char *)config.GetValue(buttonName, "shiftvalue"));
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            shift_value = keyVal;
                        else continue;

                        keyVal = config.GetValue(buttonName, "shiftlabel");
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            shift_label = keyVal;
                        else continue;

                        keyVal = StermValueParser::Parse((char *)config.GetValue(buttonName, "ctrlvalue"));
#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            ctrl_value = keyVal;
                        else continue;

                        keyVal = config.GetValue(buttonName, "ctrllabel");

#ifdef __debug__
                        printf("%s\n", keyVal);
#endif
                        if(keyVal)
                            ctrl_label = keyVal;
                        else continue;

                        section->AddButton(new StermButton(screen, row, col, new StermKey(value, label, shift_value, shift_label, ctrl_value, ctrl_label), bgcolor, fgcolor), sectionrow, sectioncol);
                    }
                }
                stermKeyboard->AddSection(section_id, section);
            }
        }

        stermKeyboard->SelectSection(2);
    }
    return 0;
}

//----------------------------------------------------------------------------//
//                                                                            //
//----------------------------------------------------------------------------//
int main(int argc, char **argv)
{
    CONFIG_FILE = "termula2x.ini";
    EXTRA_ARGC = 0;

    for (int i = 1; i < argc; ++i) {
      if (!strcmp( argv[i], "-help")) {
        fprintf(stdout, "syntax: termlua2x [-help] [-ini filename] [args ...]\n");
        fprintf(stdout, " -help        : this help\n");
        fprintf(stdout, " -ini filename: specify another ini/config filename\n");
        fprintf(stdout, " args ....    : extra arguments for the ash shell\n");
        return 0;
      } else
      if (!strcmp( argv[i], "-ini")) {
         if (++i < argc) {
           CONFIG_FILE = argv[i];
         }
      } else {
        EXTRA_ARGV = &argv[i];
        EXTRA_ARGC = argc - i;
        break;
      }
    }

    load_config();
    if(usbkeyboard)
        SDL_EnableUNICODE(1);

    bool esc_mode = false;
    bool cursor = false;
    int counter = 5;

    Touchscreen ts;
    bool has_TS = false;
    has_TS = ts.init(); // init touchscreen

    struct timeval start_time;
    gettimeofday(&start_time, NULL);

    while(1)
    {
        // poll SDL event every 75 ms
        struct timeval tv;
        gettimeofday(&tv, NULL);    // tv = current time

        if((tv.tv_sec - start_time.tv_sec) * 1000000 +
                (tv.tv_usec - start_time.tv_usec) > PERIOD)
        {
            if(has_TS && vkeyboard) {
                ts.poll();
                if(ts.pressed()) {
                    process_TS_Event(ts.x, ts.y);
                }
            }
            process_SDL_Event();

            start_time = tv;
            if(!--counter)
            {
                if(cursor){
                    hide_cursor();
                    cursor = false;
                    counter = 5;
                }
                else{
                    show_cursor();
                    cursor = true;
                    counter = 5;
                }
            }
        }

        // calculate remaining time for select()
        tv.tv_sec = start_time.tv_sec - tv.tv_sec;
        tv.tv_usec = start_time.tv_usec + PERIOD - tv.tv_usec;

        if(tv.tv_usec >= 1000000)
        {
            ++tv.tv_sec;
            tv.tv_usec -= 1000000;
        }
        else if(tv.tv_usec < 0)
        {
            --tv.tv_sec;
            tv.tv_usec += 1000000;
        }

        if(tv.tv_sec < 0)
        {
            // time goes backward
            gettimeofday(&start_time, NULL);
            continue;
        }

        fd_set rfds, wfds;
        FD_ZERO(&rfds);
        FD_ZERO(&wfds);
        FD_SET(master, &rfds);  // always check input
        if(!send_buf.empty())   // check output if there is something to send
            FD_SET(master, &wfds);

        int retval = select(master+1, &rfds, &wfds, NULL, &tv);
        if(retval == -1)
        {
            perror("select()");
            sleep(1);
        }
        else if(retval)
        {
            if(FD_ISSET(master, &rfds))
            {
                char buf[512];
                int n = read(master, buf, 512);
                if(n > 0)
                    display_string(buf, n);
            }
            else if(FD_ISSET(master,&wfds))
            {
                int n = write(master, send_buf.data(), send_buf.size());
                if(n > 0)
                    send_buf = send_buf.substr(n);
            }
        }
    }
    return 0;
}
